//
// 定义所有的 model method
// 专用的 model method 需要在自己的 model 中定义
// 因此需要 定义 Model 继承机制
//
// baseModel 对应 官方odoo.models.Model

import { domain_map } from './domain_patch.js'

class MetaModel {
  constructor(model_name, payload = {}) {
    const { env, metadata_extend = {} } = payload
    this._model = model_name
    this._env = env
    // metadata_extend 是 addons_models 中 字段的 domain 的定义
    // 在 fields_get 方法中 用到
    this.metadata_extend = metadata_extend
  }

  context_get(kwargs = {}) {
    const { context = {} } = kwargs
    const ctx = this.env.context
    const ctx2 = { ...ctx, ...context }
    return ctx2
  }

  async call_kw(method, args = [], kwargs = {}) {
    const context = this.context_get(kwargs)
    const kwargs2 = { ...kwargs, context }

    const rpc = this._env.rpc
    const web = rpc.web
    const model = this.model
    return web.dataset.call_kw(model, method, args, kwargs2)
  }

  get model() {
    return this._model
  }

  get env() {
    return this._env
  }

  async call_button(method, rids, kwargs = {}) {
    const context = this.context_get(kwargs)
    const { action_map, record, records, ...kwargs1 } = kwargs
    const kwargs2 = { ...kwargs1, context }

    const rpc = this._env.rpc
    const web = rpc.web
    const model = this.model
    const args = [rids]
    return web.dataset.call_button(model, method, args, kwargs2)
  }
}

function format_domain(kws) {
  const { meta, fname, Model } = kws
  // console.log(meta.domain)
  if (!meta.domain) {
    return meta
  }

  if (Array.isArray(meta.domain)) {
    return meta
  }

  if (typeof meta.domain !== 'string') {
    return meta
  }

  const domain_str = meta.domain.trim()
  // const first_char = domain_str.slice(0, 1)
  // const last_char = domain_str.slice(domain_str.length - 1)

  // if (first_char !== '[') {
  //   return meta
  // }

  // if (last_char !== ']') {
  //   return meta
  // }

  if (domain_str in domain_map) {
    const domain = domain_map[domain_str]
    return { ...meta, domain }
  }

  // console.log(Model.model, fname, meta.domain)

  return meta
}

export class BaseModel extends MetaModel {
  constructor(...args) {
    super(...args)
  }

  async _fields_get(allfields, kwargs = {}) {
    const { attributes, context = {} } = kwargs
    const method = 'fields_get'
    const args = [allfields, attributes]
    return this.call_kw(method, args, { context })
  }

  async fields_get(allfields, kwargs = {}) {
    const fs = await this._fields_get(allfields, kwargs)
    return Object.keys(fs).reduce((acc, fld) => {
      const ext = this.metadata_extend[fld] || {}
      const meta = { ...fs[fld], ...ext }

      if (meta.relation) {
        //

        const meta2 = format_domain({ meta, fname: fld, Model: this })

        acc[fld] = meta2
      } else {
        acc[fld] = meta
      }

      return acc
    }, {})
  }

  async search_count(domain, kwargs = {}) {
    // const { limit, context = {} } = kwargs
    const method = 'search_count'
    const args = [domain]
    return this.call_kw(method, args, kwargs)
  }

  async search(domain = [], kwargs = {}) {
    const method = 'search'
    const args = [domain]
    return this.call_kw(method, args, kwargs)
  }

  async search_one(domain) {
    const ids = await this.search(domain, { limit: 1 })
    if (ids.length >= 1) {
      return ids[0]
    } else {
      return
    }
  }

  async name_search(kwargs = {}) {
    // const { name, args, operator, limit } = kwargs
    const method = 'name_search'
    return this.call_kw(method, [], kwargs)
  }

  // from odoo 17. 该方法不推荐使用
  async name_get(ids, kwargs = {}) {
    const method = 'name_get'
    const args = [ids]
    return this.call_kw(method, args, kwargs)
  }

  async search_read(kwargs = {}) {
    const method = 'search_read'
    const args = []
    return this.call_kw(method, args, kwargs)
  }

  async web_search_read(domain, specification, kwargs = {}) {
    // 按照 odoo17 定义参数格式
    // domain, specification
    //     odoo17 为必填参数. odoo16为选填
    // specification
    //     odoo17 Dict, 嵌套格式
    //     odoo16 List, 不能嵌套 旧参数名为 fields
    // 返回值: relation字段的值,
    //   odoo17开始有变化
    //     m2o 返回 {id}
    //     o2m/m2m 返回 [id], [{id}] ?

    const method = 'web_search_read'
    const args = [domain, specification]
    return this.call_kw(method, args, kwargs)
  }

  async web_read(ids, specification, kwargs = {}) {
    // odoo17 新增.
    // 返回值: relation字段的值
    //   m2o 返回 {id}
    //   o2m/m2m 返回 [id], [{id}] ?
    const method = 'web_read'
    const args = [ids, specification]
    return this.call_kw(method, args, kwargs)
  }

  async read(ids, fields, kwargs = {}) {
    // 根据 odoo17的格式. fields 为必填
    const method = 'read'
    const args = [ids, fields]
    return this.call_kw(method, args, kwargs)
  }

  async read_one(res_id, fields, kwargs = {}) {
    const res = await this.read(res_id, fields, kwargs)
    if (res.length) {
      return res[0]
    } else {
      return {}
    }
  }

  async onchange(ids, values, field_name, fields_spec, kwargs = {}) {
    // 参数 fields_spec: Dict
    //   for odoo16. 旧参数名 field_onchange 指明哪些字段前端需要重新计算. 是扁平结构
    //   for odoo17. 指明哪些字段重新计算后, 需要被读取. 是嵌套结构
    // 返回结果  {value, domain, warning}
    //   for odoo16.  value 需要在前端嵌套处理 m2m/o2m, 在做标准化+非空
    //   for odoo17.  value 返回嵌套的值. 无需嵌套. 直接做标准化+非空
    const method = 'onchange'
    const args = [ids, values, field_name, fields_spec]
    const res = await this.call_kw(method, args, kwargs)
    return res
  }

  async create(vals, kws = {}) {
    const method = 'create'
    const args = [vals]
    return this.call_kw(method, args, kws)
  }

  async write(rid, vals, kws = {}) {
    const method = 'write'
    const args = [rid, vals]
    return this.call_kw(method, args, kws)
  }

  async unlink(rid) {
    const method = 'unlink'
    const args = [rid]
    return this.call_kw(method, args)
  }

  async web_save(rid, vals, specification, kws = {}) {
    // odoo17 新增
    // 参数 ids: 空表示 create. 非空表示 write
    // 参数 specification 返回值 包含哪些字段
    // 返回结果. 同 web_read
    const method = 'web_save'
    const args = [rid, vals, specification]
    return this.call_kw(method, args, kws)
  }
}
